Simple drawing and Animation inside canvas with React
Sometimes hard to find some basic steps to make some features, for me some time ago it was integration react with canvas and adding animation. Here I'll show basic steps that should help to jump into this stack and made a simple interaction.
Let's start with some code.
Define our first component and draw first rectangle figure.
function CanvasComponent() {const canvasRef = useRef(null);useEffect(() => {let canvas = canvasRef.current;let ctx = canvas.getContext("2d");ctx.fillStyle = "tomato";ctx.fillRect(10, 10, 50, 50);});return <canvas ref={canvasRef} width="300" height="200" />;}
Initialize link to our dom element with help of useRef, and now we have a connection with our canvasт, and then through useEffect
we are getting inner canvas context engine (ctx
) and draw a rectangle.
We have our rectangle, time to add animation
To initialize animation on the canvas we need to write our custom function that will be responsible for animation with the help of requestAnimationFrame
function widthAnimation(start, ctx) {return function step(timestamp) {if (!start) start = timestamp;let progress = timestamp - start;let limit = 280;let xValue = Math.min(progress / 10, limit);ctx.fillRect(xValue, 10, 50, 50);if (xValue < limit) {window.requestAnimationFrame(step);}};}
Passing 2 arguments inside to our function, first one it's initial value for step, and the second one its context of our canvas that we are saving to the scope with the first initialization, this function return another recursive function step
that is will be repeated till our value do not get threshold in our case it's 200 ( limit = 280
).
And now we call our animation function inside useEffect, inside animationFunc
we are passing our parameters, and then call animation window.requestAnimationFrame(animation
useEffect(() => {const canvas = canvasRef.current;const ctx = canvas.getContext("2d");ctx.fillStyle = "tomato";ctx.fillRect(10, 10, 50, 50);let animationFunc = widthAnimation(null, ctx);window.requestAnimationFrame(animationFunc);});
Something goes wrong, we wanted to move rectangle from one corner to another, but we just increase his width
This is specific of canvas drawing, after each drawing we have previous state of all our drawing on the canvas, it means all this path from 0 to 280 we have multiple amounts of rectangels. To fix this we need clear our canvas context before each drawFunction like this ctx.clearRect(0,0,300,200
return function step(timestamp) {//...ctx.clearRect(0, 0, 300, 200)ctx.fillRect(xValue, 10, 50, 50)//...}
/ gif with correct behavior
Controls for Animation
Here is how we can do our animation contollable
Creating useState
for toggle animation
const [isActive, toggleAnimation] = useState(false);
Adding controls to Run and Reset state
<div><buttonstyle={styles.button}type="button"onClick={() => toggleAnimation(true)}>Run</button><buttonstyle={styles.button}type="button"onClick={() => toggleAnimation(false)}>Reset</button></div>
And now we need add to our useEffect
condition at the onset of which we are running animattion
useEffect(() => {const canvas = canvasRef.current;const ctx = canvas.getContext("2d");let animationFunc = widthAnimation(null, ctx);let animation = null;ctx.clearRect(0, 0, 300, 200);ctx.fillStyle = "tomato";ctx.fillRect(10, 10, 50, 50);if (isActive) {animation = window.requestAnimationFrame(animationFunc);}return () => {window.cancelAnimationFrame(animation);};});
One important part, we should clear / reset our hook with the return function , it's should be done to prevent performance issue and possible bug
useEffect(() => {...return () => {window.cancelAnimationFrame(animation)}})
As a conclusion
Here we are met how is working simple drawing inside canvas with React, and add basic animation interaction requestAnimationFrame
with useEffect.